[Rust] RusqliteでSqliteを操作する
Introduction
Rustでsqliteを操作するためのcrateはたくさんありますが、
今回はRusqliteというcrateを使って
Sqliteにアクセスしてみました。
RustからPostgreSQLにアクセスするcrate、rust-postgresをベースに作成されており、
似たようなインターフェイスで使うことが可能になっています。
Environment
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 11.3.1
- rust : 1.61.0
Try
Sqliteのセットアップ
まずはsqlite3をHomebrewでインストールします。
% brew install sqlite3
インストールできたらexample_db.db3と名前指定して起動。
% sqlite3 example_db.db3 sqlite>
テーブル作成とテストデータを登録しておきましょう。
create table if not exists person ( id integer primary key, department text not null, name text not null unique, salary integer not null ); insert into person values(1,'development','Taro',120); insert into person values(2,'development','Hanako',200); insert into person values(3,'development','Syuta',260); insert into person values(4,'sales','Mike',160); insert into person values(5,'sales','Takeshi',240); insert into person values(6,'sales','Kyousuke',100); insert into person values(7,'sales','Teru',220); insert into person values(8,'management','Hide',400); insert into person values(9,'management','Marry',200); insert into person values(10,'management','Kim',300);
Rust実装
Cargoでプロジェクト作成後、Cargo.tomlでライブラリの指定をします。
% cargo new rusqlite-example % cd rusqlite-example
[dependencies] rusqlite = "*"
main.rsにsqliteへアクセスするためのコードを書いていきます。
まずはuseと構造体の定義。
さきほどcreate tableしたpersonテーブルに対応する構造体を定義します。
avg_salaryは、部署ごとの平均salaryを示すフィールドです。
use rusqlite::{params, Connection, Result}; #[derive(Debug)] struct Person{ id: u16, department:String, name: String, salary: u32, avg_salary:Option<f32> }
次はコネクション取得関数を定義。
Connection::openでsqliteファイルを渡せばOK。
なお、Connection::open_in_memory関数を使えばインメモリで起動します。
fn open_my_db() -> Result<Connection,rusqlite::Error> { let path = "./example_db.db3"; let con = Connection::open(&path)?; println!("{}", con.is_autocommit()); Ok(con) }
データinsert用関数はこんな感じです。
シンプル。
fn insert_person(con:&Connection,p:&Person) -> Result<usize,rusqlite::Error> { return Ok(con.execute( "insert into person (department,name, salary) values (?1, ?2,?3)", params![p.department,p.name, p.salary] )?); }
personテーブル全件取得してprintlnする関数です。
query_mapでPerson配列をつくってます。
fn select_all(con:&Connection){ let mut stmt = con.prepare("select id,department,name,salary from person").unwrap(); let persons = stmt.query_map(params![], |row| { Ok(Person { id: row.get(0).unwrap(), department: row.get(1).unwrap(), name: row.get(2).unwrap(), salary: row.get(3).unwrap(), avg_salary:None }) }).unwrap(); for p in persons { println!("{:?}", p.unwrap()); } }
sqliteをすっごく久しぶりにさわったので、window関数が実装されてたのとか知りませんでした。
(3.25から実装とのこと)
こんな感じでsql書いて、departmentごとの平均salaryを各レコードに計算してます。
fn select_window(con:&Connection) { let mut stmt = con.prepare("select id,department,name,salary,avg(salary) over defw from person window defw as (partition by department)").unwrap(); let persons = stmt.query_map(params![], |row| { Ok(Person { id: row.get_unwrap(0), department: row.get_unwrap(1), name: row.get_unwrap(2), salary: row.get_unwrap(3), avg_salary:Some(row.get_unwrap(4)) }) }).unwrap(); for p in persons { println!("{:?}", p.unwrap()); } } fn main() { let con = open_my_db().unwrap(); select_window(&con); }
Summary
とても簡単にsqliteにアクセスできました。
sqliteは今後お世話になる可能性があるので、使い方をおさらいしておきたいところです。